#include <ctime>

#include "Scene.h"
#include "Exception.h"
#include "Logger.h"
#include "Helpers.hpp"
#include "GLUtils.h"

using namespace game_engine;
using namespace game_engine::open_gl;
using namespace game_engine::utils;

///////////////////////////////////////////////////////////////////////////////
// Constructors and Destructors
///////////////////////////////////////////////////////////////////////////////

Scene::Scene(void)
    : name(), resolution(), activeCamera(NULL), nextScene(NULL)//, sceneManager()
{
    for (INT32 i=0; i<MAX_KEYS; i++)
    {
        keys[i] = false;
    }

    mouseButtonUp.clear();
    mouseButtonDown.clear();

    //sceneManager.setScene(this);
    objects.clear();
    objectsByName.clear();
}

Scene::Scene(const string &n)
    : name(n), resolution(), activeCamera(NULL), nextScene(NULL)//, sceneManager()
{
    for (INT32 i=0; i<MAX_KEYS; i++)
    {
        keys[i] = false;
    }

    mouseButtonUp.clear();
    mouseButtonDown.clear();

    //sceneManager.setScene(this);
    objects.clear();
    objectsByName.clear();
}

Scene::~Scene(void)
{
    // TODO : delete objects??
}

///////////////////////////////////////////////////////////////////////////////
// Other public methods
///////////////////////////////////////////////////////////////////////////////

void Scene::addObject(GameObject *newObj)
{
    objects.push_back(newObj);
    //objects.sort();

    map<string, GameObject*>::const_iterator it;
    if (findObject(newObj->getName(), it))
    {
        string err("object ");
        err.append(newObj->getName());
        err.append(" already exists in current scene");
        throw IllegalArgumentException(err);
    }
    objectsByName[newObj->getName()] = newObj;
    Logger::INFO(GAMEENGINE_LOG, string("Added new object to scene : ") + 
        newObj->getName());
}

void Scene::removeObject(GameObject *obj)
{
    // TODO : check for existance

    objectsByName.erase(obj->getName());
    objects.remove(obj);
    delete obj;
    //objects.sort();
}

void Scene::removeObject(const string &objName)
{
    // TODO : check for existance

    GameObject *obj = objectsByName[objName];
    objectsByName.erase(objName);
    objects.remove(obj);
    delete obj;
    //objects.sort();
}

const BOOL Scene::keyPressed(const UINT32 key) const
{
    return keys[key];
}

void Scene::forceKeyState(const UINT32 key, const BOOL pressed)
{
    keys[key] = pressed;
}

const BOOL Scene::isButtonUp(const INT32 button, Point &position)
{
    BOOL found = false;

    map<INT32, Point>::const_iterator it;
    it = mouseButtonUp.find(button);
    // If element is found
    if (it != mouseButtonUp.end())
    {
        found = true;
        position = mouseButtonUp[button];
    }

    return found;
}

const BOOL Scene::isButtonDown(const INT32 button, Point &position)
{
    BOOL found = false;

    map<INT32, Point>::const_iterator it;
    it = mouseButtonDown.find(button);
    // If element is found
    if (it != mouseButtonDown.end())
    {
        found = true;
        position = mouseButtonDown[button];
    }

    return found;
}

const BOOL Scene::initialize(void)
{
    BOOL result = true;

    // Initialize game objects
    try
    {
        initGameObjects();
        //sceneManager.initialize();
    } catch (exception &e)
    {
        Logger::ERR(GAMEENGINE_LOG, string("Error during scene initialization: ")
            + e.what());
        return false;
    }

    nextScene = this;

    return result;
}

const BOOL Scene::loop(void)
{
    BOOL res = true;

    // Check elapsed time since last call of this method
    static clock_t lastTime = clock();
	clock_t now = clock();
	FLOAT32 elapsedTime = (float) (now - lastTime) / (float) CLOCKS_PER_SEC;
	lastTime = now;
    //Logger::INFO(GAMEENGINE_LOG, string("elapsedTime = ") + to_string<FLOAT32>(elapsedTime, dec));

    res = update(elapsedTime);

    // Initialize mouse button structures
    mouseButtonUp.clear();
    mouseButtonDown.clear();

    // Redraw scene if update was correct
    if(res)
    {
        GLUtils::repaint();
    }

    return res;
}

void Scene::render(void)
{
    GLUtils::initializeRendering();

    // Render game objects
    try
    {
        renderGameObjects();
    } catch (exception &e)
    {
        Logger::ERR(GAMEENGINE_LOG, string("Error during scene rendering: ")
            + e.what());
    }

    GLUtils::finalizeRendering();
}

void Scene::finalize(void)
{
    // Finalize game objects
    try
    {
        finishGameObjects();
        //sceneManager.finalize();
    } catch (exception &e)
    {
        Logger::ERR(GAMEENGINE_LOG, string("Error during scene finalization: ")
            + e.what());
    }
}

void Scene::readKeyboard(const UCHAR key, const INT32 x, 
    const INT32 y, const BOOL press)
{
    keys[key] = press;
}

void Scene::readMouse(const INT32 button, const INT32 state, 
    const INT32 x, const INT32 y)
{
    //Logger::INFO(GAMEENGINE_LOG, string("X : ") + to_string<INT32>(x, dec) 
    //    + string(", Y : ") + to_string<INT32>(y, dec));
    //Logger::INFO(GAMEENGINE_LOG, string("button : ") + to_string<INT32>(button, dec) 
    //    + string(", state : ") + to_string<INT32>(state, dec));

    if (state == GLUT_UP)
    {
        mouseButtonUp[button] = Point(x, resolution.getHeight() - y);
    }
    else
    {
        mouseButtonDown[button] = Point(x, resolution.getHeight() - y);
    }
}

///////////////////////////////////////////////////////////////////////////////
// Protected methods
///////////////////////////////////////////////////////////////////////////////

void Scene::initGameObjects(void) throw(...)
{
    list<GameObject*>::const_iterator it;
    for(it=objects.begin(); it!=objects.end(); it++)
    {
        (*it)->initialize();
    }
}

void Scene::updateGameObjects(const FLOAT32 time) throw(...)
{
    list<GameObject*>::const_iterator it;
    for(it=objects.begin(); it!=objects.end(); it++)
    {
        (*it)->update(time);
    }
}

void Scene::renderGameObjects(void) throw(...)
{
    list<GameObject*>::const_iterator it;
    for(it=objects.begin(); it!=objects.end(); it++)
    {
        (*it)->render();
    }
}

void Scene::finishGameObjects(void) throw(...)
{
    list<GameObject*>::const_iterator it;
    for(it=objects.begin(); it!=objects.end(); it++)
    {
        (*it)->finalize();
    }
}

///////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////

const BOOL Scene::findObject(const string &objName, 
                             map<string, GameObject*>::const_iterator &it) 
                             const
{
    BOOL found = false;
    it = objectsByName.find(objName);
    if (it != objectsByName.end())
    {
        found = true;
    }
    return found;
}

///////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////

const string& Scene::getName(void) const
{
    return name;
}

const Size& Scene::getResolution(void) const
{
    return resolution;
}

GameObject* Scene::getActiveCamera(void) const
{
    return activeCamera;
}

const list<GameObject*> Scene::getObjects(void) const
{
    return objects;
}

GameObject* Scene::getObjectByName(const string &objName) const throw(...)
{
    map<string, GameObject*>::const_iterator it;
    if (!findObject(objName, it))
    {
        string err("object ");
        err.append(objName);
        err.append(" not found");
        throw NoSuchElementException(err);
    }
    return (*it).second;
}

//vector<GameObject*> Scene::getObjectsByTag(const string &tagName) const
//{
//
//}

Scene* Scene::getNextScene(void) const
{
    return nextScene;
}

///////////////////////////////////////////////////////////////////////////////
// Setters
///////////////////////////////////////////////////////////////////////////////

void Scene::setName(const string &n)
{
    name = n;
}

void Scene::setResolution(const Size &res)
{
    resolution = res;
}

void Scene::setActiveCamera(GameObject *cam)
{
    // Verify if the object is really a camera, i.e. if it contains a
    // component of type "Camera"
    // Otherwise, throw exception
    if (!cam->hasComponent("Camera"))
    {
        string err("object ");
        err.append(cam->getName());
        err.append(" is not a Camera");
        Logger::ERR(GAMEENGINE_LOG, err);
        throw IllegalArgumentException(err);
    }

    activeCamera = cam;
}
